#include "stdafx.h"
#include "layoutDesc.h"
#include "layoutBuilder.h"
#include "textTokens.h"

//---------------------------------------------------------

CLayoutBuilder::CLayoutBuilder( IErrorReporter& errorReporter, CLayoutDefinition* def )
	: m_definition( def  )
	, m_errorReporter( &errorReporter )
	, m_file( "unknown" )
	, m_line( 0 )
{
}

CLayoutGroup* CLayoutBuilder::AddGroup( const std::string& name, const bool isMessage )
{
	// find the group
	CLayoutGroup* group = m_definition->GetGroup( name, isMessage );
	if ( !group )
	{
		EmitError( "Unable to create group '%s'", name.c_str() );
		return nullptr;
	}

	// group is already defined
	if ( group->m_defined )
	{
		EmitError( "Group '%s' was already defined", name.c_str() );
		return nullptr;
	}

	// set as defined
	group->m_defined = true;

	// done
	return group;
}

void CLayoutBuilder::SetBaseGroup( CLayoutGroup* group, const std::string& baseGroupName )
{
	// already have base group ?
	if ( group->GetBaseGroup() != nullptr )
	{
		EmitError( "Group '%s' already has base group set", group->GetName().c_str() );
		return;
	}

	// get base group
	CLayoutGroup* baseGroup = m_definition->GetGroup( baseGroupName, true );
	if ( baseGroup == nullptr )
	{
		EmitError( "Group '%s' cannot be used as base group", baseGroupName.c_str() );
		return;
	}

	// set base group (NOTE: no recurrency check yet)
	group->m_baseGroup = baseGroup;
	baseGroup->m_derivedGroups.push_back( group );
}

bool CLayoutBuilder::ParseType( const std::string& name, ELayoutVarType& outVarType )
{
	if ( name == "int8" )
	{
		outVarType = eLayoutVar_Int8;
		return true;
	}
	else if ( name == "int16" )
	{
		outVarType = eLayoutVar_Int16;
		return true;
	}
	else if ( name == "int32" || name == "int" )
	{
		outVarType = eLayoutVar_Int32;
		return true;
	}
	else if ( name == "int64" )
	{
		outVarType = eLayoutVar_Int64;
		return true;
	}
	else if ( name == "uint8" )
	{
		outVarType = eLayoutVar_Uint8;
		return true;
	}
	else if ( name == "uint16" )
	{
		outVarType = eLayoutVar_Uint16;
		return true;
	}
	else if ( name == "uint32" )
	{
		outVarType = eLayoutVar_Uint32;
		return true;
	}
	else if ( name == "uint64" )
	{
		outVarType = eLayoutVar_Uint64;
		return true;
	}
	else if ( name == "bool" )
	{
		outVarType = eLayoutVar_Bool;
		return true;
	}
	else if ( name == "float" )
	{
		outVarType = eLayoutVar_Float;
		return true;
	}
	else if ( name == "double" )
	{
		outVarType = eLayoutVar_Double;
		return true;
	}
	else if ( name == "string" )
	{
		outVarType = eLayoutVar_String;
		return true;
	}

	return false;
}

CLayoutGroupElement* CLayoutBuilder::AddField( CLayoutGroup* group, const std::string& name, const std::string& type, bool isOptional, bool isArray )
{
	// make sure we don't have this attribute already defined
	if ( group->GetElement( name ) )
	{
		EmitError( "Element '%s' already defined in group '%s'", name.c_str(), group->GetName().c_str() );
		return nullptr;
	}

	// parse simple type
	ELayoutVarType varType;	
	const CLayoutGroup* varGroup = nullptr;
	if ( !ParseType( type, varType ) )
	{
		// not a simple type, try to find a group name
		varGroup  = m_definition->FindGroup( type );
		if ( !varGroup )
		{
			EmitError( "Element '%s' in group '%s' uses invalid type '%s'", name.c_str(), group->GetName().c_str(), type.c_str() );
			return nullptr;
		}

		// determine the type
		varType = eLayoutVar_Group;
	}

	// setup field
	auto* field = new CLayoutGroupElement( name, varType, isOptional, varGroup, isArray );
	group->m_elements.push_back( field );
	return field;
}

void CLayoutBuilder::SetDefaultValue( CLayoutGroupElement* elem, const std::string& defaultValue )
{
	elem->m_hasDefaultValue = true;
	elem->m_default = defaultValue;
}

bool CLayoutBuilder::SetGroupValueType( CLayoutGroup* group, const std::string& type )
{
	if ( group->m_valueType != eLayoutVar_Invalid )
	{
		EmitError( "Group '%s' has already value type set", group->GetName().c_str() );
		return false;
	}

	// parse type
	ELayoutVarType varType;	
	if ( !ParseType( type, varType ) )
	{
		EmitError( "Group '%s' uses invalid type '%s' for it's value", group->GetName().c_str(), type.c_str() );
		return false;
	}

	// set group value type
	group->m_valueType = varType;
	return true;
}

void CLayoutBuilder::EmitError( const char* message, ... )
{
	char buf[1024];
	va_list args;

	va_start( args, message );
#ifdef __linux__
	assert( message != nullptr );
	vsprintf( buf, message, args );
#else
	vsprintf_s( buf, sizeof(buf), message, args );
#endif
	va_end( args );

	m_errorReporter->EmitError( buf );
}